/** @file
Platform initialize at PEI stage.

Copyright (C) 2022 - 2023, Phytium Technology Co., Ltd. All rights reserved.<BR>
Copyright (c) 2011, ARM Limited. All rights reserved.

SPDX-License-Identifier: BSD-2-Clause-Patent

**/
#include "PlatformPei.h"

MONTH_DESCRIPTION gMonthDescription[] = {
  { "Jan", 1 },
  { "Feb", 2 },
  { "Mar", 3 },
  { "Apr", 4 },
  { "May", 5 },
  { "Jun", 6 },
  { "Jul", 7 },
  { "Aug", 8 },
  { "Sep", 9 },
  { "Oct", 10 },
  { "Nov", 11 },
  { "Dec", 12 },
  { "???", 1 },  // Use 1 as default month
};

//
// Module globals
//
CONST EFI_PEI_PPI_DESCRIPTOR  mPpiListBootMode = {
  (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
  &gEfiPeiMasterBootModePpiGuid,
  NULL
};

CONST EFI_PEI_PPI_DESCRIPTOR  mPpiListRecoveryBootMode = {
  (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
  &gEfiPeiBootInRecoveryModePpiGuid,
  NULL
};

/**
  Get reset source.

  @retval    RESET_SOURCE_POWER      Power on.
  @retval    RESET_SOURCE_PLL        Pll reset.
  @retval    RESET_SOURCE_WDT        Watchdog.
  @retval    Other                   Reserved.
**/
UINT32
GetResetSource (
  VOID
  )
{
  return  PssiGetResetSource ();
}

/**
  Get die bit map.
  If C2C of all die is disable, it means single die mode, and die0 is enabled
  by default. Otherwise, the die is enabled according to C2C enable. 8 die in
  total. Bit(n) means die(n) enable.

  @return    Map    Die bit map.
**/
UINT8
GetDieMap (
  VOID
  )
{
  UINT8                    Buffer[0x100];
  PARAMETER_C2C_CONFIG     *C2CConfig;
  UINT8                    Map;
  UINT32                   Index;

  Map = 0;
  ZeroMem (Buffer, 0x100);
  //
  //Get C2C information form parameter table.
  //
  if (EFI_SUCCESS == GetParameterInfo (PM_C2C, Buffer, 0x100)) {
  } else {
    DEBUG ((DEBUG_ERROR, "Get C2C Parameter Fail.\n"));
    Map = 0;
    goto ProcExit;
  }

  C2CConfig = (PARAMETER_C2C_CONFIG *) Buffer;
  //
  //If C2C of all die is disable, it means single die mode, and die0 is enabled
  //by default. Otherwise, the die is enabled according to C2C enable.
  //
  if (C2CConfig->C2CEnable.Uint32 == 0) {
    Map = 0x1;
  } else {
    for (Index = 0; Index < 8; Index++) {
      if ((C2CConfig->C2CEnable.Uint32 >> Index * 4) & 0xF) {
        Map |= (0x1 << Index);
      }
    }
  }

ProcExit:
  DEBUG ((DEBUG_INFO, "Die Map : 0x%02x\n", Map));
  return Map;
}

/**
  Initialized the C2C.

**/
VOID
C2CInit (
  VOID
  )
{
  PSSI_STATUS              Status;
  UINT8                    Buffer[0x100];
  PARAMETER_C2C_CONFIG     *C2CConfig;

  PostCode (POST_CODE_C2C_INIT_PARA_T_GET, 0, 0);

  ZeroMem (Buffer, 0x100);
  //
  //Get C2C information form parameter table.
  //
  if (EFI_SUCCESS == GetParameterInfo (PM_C2C, Buffer, 0x100)) {
  } else {
    PostCode (POST_CODE_C2C_INIT_PARA_T_GET_FAILED, 0, 0);
    DEBUG ((DEBUG_ERROR, "Get C2C Parameter Fail.\n"));
    while (1);
  }

  C2CConfig = (PARAMETER_C2C_CONFIG *) Buffer;
  //
  // Pssi - C2C Config
  //
  PostCode (POST_CODE_C2C_INIT_START, 0, 0);
  Status = PssiC2cInit ((VOID *) C2CConfig);
  if (Status != PSSI_SUCCESS) {
    PostCode(POST_CODE_C2C_INIT_FAILED, 0, 0);
    DEBUG ((DEBUG_ERROR, "C2C Config Failed!\n"));
    while (1);
  } else {
    DEBUG ((DEBUG_INFO, "C2C Config Success!\n"));
    PrintC2CParameter (PM_C2C, (VOID *) C2CConfig);
  }

  PostCode (POST_CODE_C2C_INIT_COMPLETE, 0, 0);
}

/**

**/
VOID
PmBusConfig (
  VOID
  )
{
  SCMI_ADDRESS_BASE          Base;
  PMBUS_CONFIG               Config;
  INT32                      ScmiStatus;
  UINT8                      DieNum;
  UINT64                     MhuConfigBase;
  UINT64                     MhuBase;
  UINT64                     ShareMemoryBase;
  EFI_STATUS                 Status;
  UINT8                      Buffer[256];
  PARAMETER_BOARD_CONFIG_V2  *BoardConfig;
  UINT32                     Index;

  Status = EFI_SUCCESS;
  ScmiStatus = SCMI_SUCCESS;
  DieNum = 0;
  //Get Board Parameter
  ZeroMem (Buffer, 256);
  //
  //Get board information form parameter table.
  //
  if (EFI_SUCCESS == GetParameterInfo (PM_BOARD_V2, Buffer, 256)) {
    BoardConfig = (PARAMETER_BOARD_CONFIG_V2 *) Buffer;
    PrintBoardParameter (PM_BOARD_V2, Buffer);
  } else {
    DEBUG ((DEBUG_ERROR, "Get Board Parameter Fail.\n"));
    return ;
  }
  ZeroMem (&Config, sizeof (PMBUS_CONFIG));
  MhuConfigBase = PcdGet64 (PcdMhuConfigBaseAddress);
  MhuBase = PcdGet64 (PcdMhuBaseAddress);
  ShareMemoryBase = PcdGet64 (PcdMhuShareMemoryBase);
  if (GetDieNumFromParTable(&DieNum) == EFI_SUCCESS) {
    for (Index = 0; Index < DieNum; Index++) {
      DEBUG ((DEBUG_INFO, "Die[%d] PMBUS Config!\n", Index));
      if (BoardConfig->PowerIc[Index].Enable & 0x1) {
        Config.SlaveAddr = BoardConfig->PowerIc[Index].SlaveAddr;
        Config.Mode = BoardConfig->PowerIc[Index].Mode;
        Config.Vlsb = BoardConfig->PowerIc[Index].Vlsb;
        Config.Ilsb = BoardConfig->PowerIc[Index].Ilsb;
        Config.Plsb = BoardConfig->PowerIc[Index].Plsb;
        Base.MhuConfigBase = MhuConfigBase + Index * 0x100000000000;
        Base.MhuBase = MhuBase + Index * 0x100000000000;
        Base.ShareMemoryBase = ShareMemoryBase + Index * 0x100000000000;
        Status = ScmiPmBusConfig (&Base, &Config, &ScmiStatus);
        if (Status == SCMI_SUCCESS) {
          DEBUG ((DEBUG_INFO, "Scmi Pmbus Config Success!\n"));
        } else {
          DEBUG ((DEBUG_ERROR, "Scmi Pmbus Config Failed!\n"));
        }
      }
    }
  }
}

/**
  Initialized the PLL form parameter table.

**/
VOID
PllInit (
  VOID
  )
{
  PSSI_STATUS              Status;
  UINT8                    Buffer[0x100];
  PARAMETER_PLL_CONFIG_V1  *PllConfig;
  UINT64                   ErrorCode;

  ErrorCode = 0;
  ZeroMem (Buffer, 0x100);
  //
  //Get PLL information form parameter table.
  //
  if (EFI_SUCCESS == GetParameterInfo (PM_PLL_V1, Buffer, 0x100)) {
    PrintPllParameter (PM_PLL_V1, Buffer);
  } else {
    DEBUG ((DEBUG_ERROR, "Get PLL Parameter Fail.\n"));
    while (1);
  }

  PllConfig = (PARAMETER_PLL_CONFIG_V1 *) Buffer;
  //
  //Pssi - Pll config.
  //
  PostCode (POST_CODE_PLL_INIT_START, 0, 0);
  Status = PssiPllInit ((VOID *) PllConfig, &ErrorCode);
  if (Status == PSSI_DENIED) {
    switch (ErrorCode) {
    case PLL_CPU_FREQ_ERROR:
      DEBUG ((DEBUG_ERROR, "Pll config cpu frequece error!\n"));
      break;
    case PLL_DDR_FREQ_ERROR:
      DEBUG ((DEBUG_ERROR, "Pll config ddr frequece error!\n"));
      break;
    default:
      DEBUG ((DEBUG_ERROR, "Pll config other frequece error - 0x%llx!\n", ErrorCode));
      break;
    }
    while (1);
  } else if (Status == PSSI_SUCCESS) {
    PostCode (POST_CODE_PLL_INIT_COMPLETE, 0, 0);
    DEBUG ((DEBUG_INFO, "Pll config success!\n"));
  } else {
    PostCode (POST_CODE_PLL_INIT_FAIL, 0, 0);
    DEBUG ((DEBUG_ERROR, "Pll config failed!\n"));
    while (1);
  }
}


/**
  Initialized the PCIE module.

**/
VOID
PcieInit (
  VOID
  )
{
  PSSI_STATUS              Status;
  UINT8                    Buffer[0x100];
  PARAMETER_PCIE_CONFIG_V3 *PcieConfig;
  UINT64                   ErrorCode;
  UINT32                   Index;
  UINT8                    DieNum;

  ErrorCode = 0;
  DieNum = 0;

  PostCode (POST_CODE_PCIE_INIT_PARA_T_GET, 0, 0);
  Status = GetDieNumFromParTable(&DieNum);
  if (Status == EFI_SUCCESS) {
    DEBUG ((DEBUG_INFO, "Die Number : %d\n", DieNum));
    for (Index = 0; Index < DieNum; Index++) {
      ZeroMem (Buffer, 0x100);
      PostCode (POST_CODE_PCIE_INIT_PARA_T_GET, 0, 0);
      if (EFI_SUCCESS == GetParameterPcieV3 (PM_PCIE_V3, Index, Buffer, 0x100)) {
        PrintPcieParameter (PM_PCIE_V3, Buffer);
        PcieConfig = (PARAMETER_PCIE_CONFIG_V3 *) Buffer;
        Status = PssiPcieInit ((VOID *) PcieConfig, &ErrorCode);
        if (Status != PSSI_SUCCESS) {
          PostCode (POST_CODE_PCIE_INIT_FAILED, Index, 0);
          DEBUG ((DEBUG_ERROR, "Die[%d] PCIe config failed - 0x%llx!\n", Index, ErrorCode));
          while (1);
        } else {
          PostCode (POST_CODE_PCIE_INIT_COMPLETE, Index, 0);
          DEBUG ((DEBUG_INFO, "Die[%d] PCIe config success!\n", Index));
        }
      } else {
        PostCode (POST_CODE_PCIE_INIT_PARA_T_GET_FAILED, Index, 0);
        DEBUG ((DEBUG_ERROR, "Get Die[%d] PCIe Parameter Fail.\n", Index));
        while (1);
      }
    }
  } else {
    DEBUG ((DEBUG_INFO,
            "Get Die Number From Parameter Table Failed : %r\n",
            Status
            ));
  }

}

/**
  Initialized the DDR module.

**/
VOID
DDRInit (
  VOID
  )
{
  PSSI_STATUS              Status;
  UINT8                    Buffer[0x100];
  PARAMETER_DDR_CONFIG_V3  *DdrConfig;
  UINT64                   ErrorCode;
  UINT32                   Index;

  ErrorCode = 0;
  ZeroMem (Buffer, 0x100);
  //
  //Get DDR information form parameter table.
  //
  PostCode (POST_CODE_MEM_INIT_PARA_T_GET, 0, 0);
  if (EFI_SUCCESS == GetParameterInfo (PM_DDR_V3, Buffer, 0x100)) {
    DdrConfig = (PARAMETER_DDR_CONFIG_V3 *) Buffer;
    GetDdrConfigParameter (DdrConfig);
    PrintDdrParameter (PM_DDR_V3, (VOID *) DdrConfig);
  } else {
    PostCode (POST_CODE_MEM_INIT_PARA_T_GET_FAILED, 0, 0);
    DEBUG ((DEBUG_ERROR, "Get DDR parameter failed!\n"));
    while (1);
  }
  //
  //Pssi ddr config
  //
  PostCode (POST_CODE_MEM_INIT_TRIAN_START, 0, 0);
  Status = PssiDdrInit ((VOID *) DdrConfig, &ErrorCode);
  if (Status != PSSI_SUCCESS) {
    DEBUG ((DEBUG_ERROR, "Ddr Config Failed : 0x%llx - 0xllx\n", Status, ErrorCode));
    if (Status == PSSI_INIT_FAIL) {
      DEBUG ((DEBUG_INFO, "Ddr training result :\n"));
      for (Index = 0; Index < 64; Index += 2) {
        //die (Index/2) dimm0
        if ((DdrConfig->ChEnableMap >> Index) & 0x1) {
          if ((ErrorCode >> Index) & 0x1) {
            PostCode (POST_CODE_MEM_INIT_TRIAN_FAILED, Index / 2, 0);
            DEBUG ((DEBUG_ERROR, "Channel[%d] : Failed!\n", Index));
          } else {
            PostCode (POST_CODE_MEM_INIT_TRIAN_COMPLETE, Index / 2, 0);
            DEBUG ((DEBUG_INFO, "Channel[%d] : Success!\n", Index));
          }
        }
        //die (Index/2) dimm1
        if ((DdrConfig->ChEnableMap >> Index) & 0x2) {
          if ((ErrorCode >> Index) & 0x2) {
            PostCode (POST_CODE_MEM_INIT_TRIAN_FAILED, Index / 2, 1);
            DEBUG ((DEBUG_ERROR, "Channel[%d] : Failed!\n", Index+1));
          } else {
            PostCode (POST_CODE_MEM_INIT_TRIAN_COMPLETE, Index / 2, 1);
            DEBUG ((DEBUG_INFO, "Channel[%d] : Success!\n", Index+1));
          }
        }
      }
      //
      //must not dimm0 and dimm1 both failed
      //
      if ((ErrorCode & 0x3) == 0x3) {
        DEBUG ((DEBUG_INFO, "dimm[0] and dimm[1] both training failed!\n"));
        while (1);
      }
    } else {
      while (1);
    }
  } else {
    //
    //All success
    //
    for (Index = 0; Index < 64; Index += 2) {
      if ((DdrConfig->ChEnableMap >> Index) & 0x1) {
        PostCode (POST_CODE_MEM_INIT_TRIAN_COMPLETE, Index / 2, 0);
      }
      if ((DdrConfig->ChEnableMap >> Index) & 0x2) {
        PostCode (POST_CODE_MEM_INIT_TRIAN_COMPLETE, Index / 2, 1);
      }
    }
    DEBUG ((DEBUG_INFO, "Ddr Config Success!\n"));
  }
}

/**
  Relocate CPU.

**/
VOID
CPURelocate (
  VOID
  )
{
  PSSI_STATUS                 Status;
  UINT8                       Buffer[0x100];
  PARAMETER_COMMON_CONFIG_V2  *CommonConfig;

  ZeroMem (Buffer, 0x100);

  if (EFI_SUCCESS == GetParameterInfo (PM_COMMON_V2, Buffer, 0x100)) {
    CommonConfig = (PARAMETER_COMMON_CONFIG_V2 *) Buffer;
    PrintCommonParameter (PM_COMMON_V2, Buffer);
  } else {
    DEBUG ((DEBUG_ERROR, "Get COMMON parameter failed!\n"));
    while (1);
  }

  CommonConfig = (PARAMETER_COMMON_CONFIG_V2 *) Buffer;

  //
  //Pssi relocate
  //
  Status = PssiRelocate (RELOCATE_POWERON, (VOID *) CommonConfig);
  if (Status != PSSI_SUCCESS) {
    DEBUG ((DEBUG_ERROR, "Relocate Failed\n"));
    while (1);
  } else {
    DEBUG ((DEBUG_INFO, "Relocate Success!\n"));
  }
}

/**
  Get the time of right now.

  @param[out] Time  The Time sturct to be stored.

**/
VOID
GetReleaseTime (
  EFI_TIME *Time
  )
{
  UINTN        Index;
  CONST CHAR8  *ReleaseDate = __DATE__;
  CONST CHAR8  *ReleaseTime = __TIME__;

  for (Index = 0; Index < 12; Index++) {
    if (0 == AsciiStrnCmp (ReleaseDate, gMonthDescription[Index].MonthStr, 3)) {
      break;
    }
  }
  Time->Month = gMonthDescription[Index].MonthInt;
  Time->Day = AsciiStrDecimalToUintn (ReleaseDate + 4);
  Time->Year = AsciiStrDecimalToUintn (ReleaseDate + 7);
  Time->Hour = AsciiStrDecimalToUintn (ReleaseTime);
  Time->Minute = AsciiStrDecimalToUintn (ReleaseTime + 3);
  Time->Second = AsciiStrDecimalToUintn (ReleaseTime + 6);

  return;
}


/**
  Initialize the platform.

  @param[in] FileHandle   Handle of the file being invoked.
  @param[in] PeiServices  Describes the list of possible PEI Services.

  @retval    EFI_SUCCESS  is executed successfully.

**/
EFI_STATUS
EFIAPI
InitializePlatformPeim (
  IN       EFI_PEI_FILE_HANDLE  FileHandle,
  IN CONST EFI_PEI_SERVICES     **PeiServices
  )
{
  EFI_STATUS                    Status;
  EFI_BOOT_MODE                 BootMode;
  CHAR8                         Buffer[100];
  UINTN                         CharCount;
  EFI_TIME                      Time;
  UINT32                        RstSource;

  ZeroMem (&Time, sizeof (EFI_TIME));
  DEBUG ((DEBUG_INFO,
    "\n%a %a Init\n\n",
    (CHAR8 *)PcdGetPtr (PcdProductManufacturer),
    (CHAR8 *)PcdGetPtr (PcdFirmwareVendor)
    ));

  //
  //version and build time
  //
  CONST CHAR16 *ReleaseString = (CHAR16 *) FixedPcdGetPtr (PcdFirmwareVersionString);
  GetReleaseTime (&Time);
  CharCount = AsciiSPrint (
    Buffer,
    sizeof (Buffer),
    "UEFI Version %s %t\n",
    ReleaseString,
    &Time
    );
  SerialPortWrite ((UINT8 *) Buffer, CharCount);

  DEBUG ((DEBUG_LOAD | DEBUG_INFO, "Platform PEIM Loaded\n"));

  //if bmc is in place, it is need to init ksc.
  if (CheckBmcInPlace () == EFI_SUCCESS) {
    KcsInit();
  }

  Status = PeiServicesSetBootMode (ArmPlatformGetBootMode ());
  ASSERT_EFI_ERROR (Status);

  PlatformPeim ();

  Status = PeiServicesGetBootMode (&BootMode);
  ASSERT_EFI_ERROR (Status);

  Status = PeiServicesInstallPpi (&mPpiListBootMode);
  ASSERT_EFI_ERROR (Status);

  if (BootMode == BOOT_IN_RECOVERY_MODE) {
    Status = PeiServicesInstallPpi (&mPpiListRecoveryBootMode);
    ASSERT_EFI_ERROR (Status);
  }

  /*POST Code Power on*/
  PostCode(POST_CODE_POWER_ON, 0, 0);

  C2CInit ();
  RstSource = GetResetSource();
  DEBUG ((DEBUG_INFO, "Check reset source : 0x%x\n", RstSource));
  if (RstSource == RESET_SOURCE_POWER) {
    DEBUG ((DEBUG_INFO, "Power Reset!\n"));
    PllInit ();
  }  else if (RstSource == RESET_SOURCE_PLL) {
    DEBUG ((DEBUG_INFO, "PLL Reset!\n"));
    PcieInit ();
    DDRInit ();
    CPURelocate ();
    PmBusConfig ();
  } else {
    DEBUG ((DEBUG_ERROR, "Unhandled reset!\n"));
    while (1);
  }

  return Status;
}
